#pragma once

#include <memory>
#include <vector>

#include <fmt/format.h>
#include <glog/logging.h>
#include <prometheus/exposer.h>
#include <prometheus/gauge.h>
#include <prometheus/registry.h>
#include <prometheus/summary.h>

#include "coin/proto/coin_telemetry.pb.h"

namespace coin2::exchange::base::strategy_util {

namespace {

bool ValidateLatencyStats(
    const coin::proto::StrategyInfoProto& strat_info,
    const coin::proto::StrategyComponentLatencyStatsProto& stats) {
  if (!strat_info.has_strategy_name()) {
    LOG(WARNING) << "No strategy name in latency stats.";
    return false;
  }
  if (stats.latency_type() ==
      coin::proto::StrategyComponentLatencyStatsProto::UNKNOWN_LATENCY_TYPE) {
    LOG(WARNING) << "No latency type in latency stats.";
    return false;
  }
  auto& acct_req = stats.account_request();
  if (!acct_req.has_market_type() ||
      !acct_req.has_exchange() ||
      !acct_req.has_api_version()) {
    LOG(WARNING) << "No mea in latency stats.";
    return false;
  }
  if (!stats.has_latency_value()) {
    LOG(WARNING) << "No latency value in latency stats.";
    return false;
  }
  return true;
}

prometheus::Labels GetLabels(
    const coin::proto::StrategyInfoProto& strat_info,
    const coin::proto::StrategyComponentLatencyStatsProto& stats) {
  prometheus::Labels labels;
  labels["strategy_name"] = strat_info.strategy_name();
  labels["latency_type"] = coin::proto::StrategyComponentLatencyStatsProto::LatencyType_Name(
      stats.latency_type());
  auto& acct_req = stats.account_request();
  labels["mea"] = fmt::format(
      "{}.{}.{}", acct_req.market_type(), acct_req.exchange(), acct_req.api_version());
  labels["symbol"] = stats.symbol();
  return labels;
}

}  // namespace

class PrometheusHealthReporter {
 public:
  PrometheusHealthReporter(const std::string& bind_addr)
    : exposer_{bind_addr},
      registry_{std::make_shared<prometheus::Registry>()},
      health_gauge_{prometheus::BuildGauge()
                          .Name("strategy_health_gauge")
                          .Help("strategy health gauge")
                          .Register(*registry_)},
      health_summary_{prometheus::BuildSummary()
                          .Name("strategy_health_summary")
                          .Help("strategy health summary")
                          .Register(*registry_)} {
    LOG(INFO) << "prometheus bind addr: " << bind_addr;
    exposer_.RegisterCollectable(registry_ );
  }

  void Write(const coin::proto::StrategyLatencyStatsProto& latency_stats) {
    for (const auto& each_stats : latency_stats.each_stats()) {
      auto& strat_info = latency_stats.strategy();
      bool valid = ValidateLatencyStats(strat_info, each_stats);
      if (valid) {
        prometheus::Labels labels = GetLabels(strat_info, each_stats);
        auto& gauge = health_gauge_.Add(labels);
        gauge.Set(static_cast<double>(each_stats.latency_value()));
      }
    }
  }

  void Write(std::vector<std::pair<prometheus::Labels, double>> labels) {
    for (const auto& info:labels) {
      auto& gauge = health_gauge_.Add(info.first);
      gauge.Set(info.second);
    }
  }

 private:
  prometheus::Exposer exposer_;
  std::shared_ptr<prometheus::Registry> registry_;
  prometheus::Family<prometheus::Gauge>& health_gauge_;
  prometheus::Family<prometheus::Summary>& health_summary_;
};  //  class PrometheusHealthReporter

}  // namespace coin2::exchange::base::strategy_util
